/**
 * \file Pic.cc
 * \brief Programmable interrupt controller
 * \author Corey Tabaka
 */

/*
   Copyright 2006 Corey Tabaka

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#include <ktypes.h>
#include <platform/Pic.h>
#include <platform/Interrupt.h>
#include <platform/msr.h>
#include <platform/processor.h>
#include <Sync.h>
#include <Namespace.h>

#include <i386/io.h>
#include <i386/console.h>
#include <stdio.h>

#include <panic.h>

#define PIC1 0x20
#define PIC2 0xA0

#define ICW1 0x11
#define ICW4 0x01

static SMPLOCK(InterruptLock) picLock;

/*
 * APIC present flag
 */
static Var apicPresent("hw.apic", 0LL, VF_SYSTEM);

/*
 * APIC base address. Only valid if apicPresent is true.
 */
static Var apicBase("hw.apic.base", 0LL, VF_SYSTEM);

/*
 * Edge/Level sensitive control register values
 */
static uint8 levelIRQ[2];

/*
 * Cached IRQ mask (enabled/disabled)
 */
static uint8 irqMask[2];

static void map(uint32 pic1, uint32 pic2);

void Pic::initialize(void) {
	// rebase the PIC out of the way of processor exceptions
	map(PIC1_BASE, PIC2_BASE);
	
	// save the edge/level sensitive settings
	levelIRQ[0] = inp(0x4d0);
	levelIRQ[1] = inp(0x4d1);
	
	// detect APIC by checking bit 9 of edx after CPUID 1
	apicPresent = cpuid_edx(1) & 0x200 ? true : false;
	
	if (apicPresent.ivalue) {
		// read APIC address from the APICBASE MSR
		uint32 apicLow, apicHigh;
		rdmsr(MSR_IA32_APICBASE, apicLow, apicHigh);
		
		// extract the base
		apicBase = apicLow & MSR_IA32_APICBASE_BASE;
		
		printf("        LAPIC at %08x\n", (uint32) apicBase.ivalue);
		
		Namespace::registerVar(apicPresent);
		Namespace::registerVar(apicBase);
	}
}

/* map()
 * init the PICs and remap them
 */
static void map(uint32 pic1, uint32 pic2) {
	picLock.lock();
	
	/* send ICW1 */
	outp(PIC1, ICW1);
	outp(PIC2, ICW1);

	/* send ICW2 */
	outp(PIC1 + 1, pic1);	/* remap */
	outp(PIC2 + 1, pic2);	/*  pics */

	/* send ICW3 */
	outp(PIC1 + 1, 4);	/* IRQ2 -> connection to slave */
	outp(PIC2 + 1, 2);

	/* send ICW4 */
	outp(PIC1 + 1, 5);
	outp(PIC2 + 1, 1);

	/* disable all IRQs */
	outp(PIC1 + 1, 0xff);
	outp(PIC2 + 1, 0xff);
	
	irqMask[0] = 0xff;
	irqMask[1] = 0xff;
	
	picLock.unlock();
}

void Pic::enable(uint8 irq, bool enable) {
	picLock.lock();
	
	if (irq >= PIC1_BASE && irq < PIC1_BASE + 8) {
		irq -= PIC1_BASE;
		
		uint8 bit = 1 << irq;
		
		if (enable && (irqMask[0] & bit)) {
			irqMask[0] = inp(PIC1 + 1);
			irqMask[0] &= ~bit;
			outp(PIC1 + 1, irqMask[0]);
			irqMask[0] = inp(PIC1 + 1);
		} else if (!enable && !(irqMask[0] & bit)) {
			irqMask[0] = inp(PIC1 + 1);
			irqMask[0] |= bit;
			outp(PIC1 + 1, irqMask[0]);
			irqMask[0] = inp(PIC1 + 1);
		}
	} else if (irq >= PIC2_BASE && irq < PIC2_BASE + 8) {
		irq -= PIC2_BASE;
		
		uint8 bit = 1 << irq;
		
		if (enable && (irqMask[1] & bit)) {
			irqMask[1] = inp(PIC2 + 1);
			irqMask[1] &= ~bit;
			outp(PIC2 + 1, irqMask[1]);
			irqMask[1] = inp(PIC2 + 1);
		} else if (!enable && !(irqMask[1] & bit)) {
			irqMask[1] = inp(PIC2 + 1);
			irqMask[1] |= bit;
			outp(PIC2 + 1, irqMask[1]);
			irqMask[1] = inp(PIC2 + 1);
		}
		
		bit = 1 << (INT_PIC2 - PIC1_BASE);
		
		if (irqMask[1] != 0xff && (irqMask[0] & bit)) {
			irqMask[0] = inp(PIC1 + 1);
			irqMask[0] &= ~bit;
			outp(PIC1 + 1, irqMask[0]);
			irqMask[0] = inp(PIC1 + 1);
		} else if (irqMask[1] == 0 && !(irqMask[0] & bit)) {
			irqMask[0] = inp(PIC1 + 1);
			irqMask[0] |= bit;
			outp(PIC1 + 1, irqMask[0]);
			irqMask[0] = inp(PIC1 + 1);
		}
	} else {
		//bug("Invalid PIC interrupt: %02x\n", irq);
	}
	
	picLock.unlock();
}

bool Pic::isOwned(uint8 interrupt) {
	return (interrupt >= PIC1_BASE && interrupt <= PIC1_BASE + 7) ||
		(interrupt >= PIC2_BASE && interrupt <= PIC2_BASE + 7);
}

void Pic::issueEOI(uint8 interrupt) {
	if (interrupt >= PIC1_BASE && interrupt <= PIC1_BASE + 7) {
		outp(PIC1, 0x20);
	} else if (interrupt >= PIC2_BASE && interrupt <= PIC2_BASE + 7) {
		outp(PIC2, 0x20);
		outp(PIC1, 0x20);	// must issue both for the second PIC
	}
}

void Pic::disableAllIRQs(void) {
	/* disable all IRQs */
	irqMask[0] = inp(PIC1 + 1);
	irqMask[1] = inp(PIC2 + 1);
	
	outp(PIC1 + 1, 0xff);
	outp(PIC2 + 1, 0xff);
	
	irqMask[0] = inp(PIC1 + 1);
	irqMask[1] = inp(PIC2 + 1);
}

bool Pic::isPending(uint8 irq) {
	if ((irq >= PIC1_BASE && irq < PIC1_BASE + 8)
		|| (irq >= PIC2_BASE && irq < PIC2_BASE + 8)) {
		irq -= PIC1_BASE;
		int irqmask = 1<<(irq&7);
		
		if (irq & 8) {
			return (inp(PIC2) & irqmask) != 0;
		} else {
			return (inp(PIC1) & irqmask) != 0;
		}
	} else {
		return false;
	}
}

bool Pic::isLevel(uint8 irq) {
	if ((irq >= PIC1_BASE && irq < PIC1_BASE + 8)
		|| (irq >= PIC2_BASE && irq < PIC2_BASE + 8)) {
		irq -= PIC1_BASE;
		int irqmask = 1<<(irq&7);
		
		if (irq & 8) {
			return (levelIRQ[1] & irqmask) != 0;
		} else {
			return (levelIRQ[0] & irqmask) != 0;
		}
	} else {
		return false;
	}
}
